/*
 * vm.c
 *
 *  Created on: 2009-11-30
 *      Author: Bearice
 */

#include <vm.h>
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
#include <gc.h>

#define asUInt32(x) (x->val.uival)
#define asUInt64(x) (x->val.ulval)
#define asInt32(x)  (x->val.ival)
#define asInt64(x)  (x->val.lval)
#define asDouble(x) (x->val.dval)
#define asFloat(x)  (x->val.fval)
#define asPtr(x)    (x->val.pval)
#define asPairL(x)  (x->val.pair.left)
#define asPairR(x)	(x->val.pair.right)
#define isInteger(x) (x->tag==TAG_INTEGER)
#define isDouble(x)  (x->tag==TAG_DOUBLE)
#define isNumber(x)  (isInteger(x)||isDouble(x))
#define toDouble(x) \
	((x->tag==TAG_DOUBLE) \
		? asDouble(x) \
		: (double)((int32)x->val.ival))

struct avm_object* popObject(struct avm_frame* frame) {
    if (frame->stack->idx == 0)
        return 0; //fails;
    struct avm_object* obj = frame->stack->ptr[--frame->stack->idx];
    DEBUG("popObject()=0x%08x\n", obj);
    return obj;
}

struct avm_object* pushObject(struct avm_frame* frame,
        struct avm_object* obj) {
    if (frame->stack->idx == frame->stack->size)
        return 0; //fails;
    gc_inclref(obj);
    DEBUG("pushObject(obj=0x%08x)\n", obj);
    return frame->stack->ptr[frame->stack->idx++] = obj;
}

uint32 popUInt32(struct avm_frame* frame) {
    struct avm_object* obj = popObject(frame);
    if (!isInteger(obj))
        return 0; //fails
    uint32 ret = asUInt32(obj);
    gc_free(obj);
    return ret;
}

uint32 pushUInt32(struct avm_frame* frame, uint32 val) {
    struct avm_object* obj = pushObject(frame, gc_alloc());
    obj->tag = TAG_INTEGER;
    return obj->val.ival = val;
}

double popDouble(struct avm_frame* frame) {
    struct avm_object* obj = popObject(frame);
    if (!isDouble(obj))
        return 0; //fails
    double ret = asDouble(obj);
    gc_free(obj);
    return ret;
}

double pushDouble(struct avm_frame* frame, double val) {
    struct avm_object* obj = pushObject(frame, gc_alloc());
    obj->tag = TAG_DOUBLE;
    return obj->val.dval = val;
}

struct avm_object* frame_heap_get(struct avm_frame* fr, uint32 idx) {
    struct avm_object* obj;
    if (fr->closure) {
        if ((obj = heap_get(fr->closure, idx)) != -1) {
            return obj;
        }
    }
    if ((obj = heap_get(&fr->heap, idx)) != -1) {
        return obj;
    }
    if (fr->parent) {
        return frame_heap_get(fr->parent, idx);
    }
    return NULL;
};

struct avm_object* frame_heap_set(struct avm_frame* fr, uint32 idx, struct avm_object* obj) {
    if (fr->closure) {
        if (heap_get(fr->closure, idx) != -1) {
            return heap_set(fr->closure, idx, obj);
        }
    }
    if (fr->parent) {
        if (frame_heap_get(fr->parent, idx) != -1) {
            return frame_heap_set(fr->parent, idx, obj);
        };
    }
    return heap_set(&fr->heap, idx, obj);
};

struct avm_object* heap_get(struct avm_heap* heap, uint32 idx) {
    if (heap->size <= idx)return NULL; //should fails
    return heap->ptr[idx];
}

struct avm_object* heap_set(struct avm_heap* heap, uint32 idx, struct avm_object* obj) {
    if (heap->size <= idx)return NULL; //should fails
    //if(obj)gc_inclref(obj);
    DEBUG("heap_set(idx=%d,obj=0x%08x)\n", idx, obj);
    if (heap->ptr[idx])gc_free(heap->ptr[idx]);
    return heap->ptr[idx] = obj;
}

struct avm_heap* heap_alloc(struct avm_heap* heap, uint32 size) {
    memset(heap, 0, sizeof (struct avm_heap));
    heap->ptr = malloc(sizeof (void*) * size);
    memset(heap->ptr, -1, sizeof (void*) * size);
    heap->size = size;
    return heap;
}

uint32 heap_free(struct avm_heap* heap) {
    int i;
    for (i = 0; i < heap->size; i++) {
        heap_set(heap, i, NULL);
    }
    free(heap->ptr);
    return i;
}

struct avm_frame* frame_alloc(struct avm_frame* ref) {
    struct avm_frame* fr = malloc(sizeof (struct avm_frame));
    if (ref) {
        memcpy(fr, ref, sizeof (struct avm_frame));
        fr->parent = ref;
        heap_alloc(&fr->heap, ref->heap.size);
    } else {
        memset(fr, 0, sizeof (struct avm_frame));
        fr->loader = malloc(sizeof (struct avm_loader_status));
    }
    return fr;
}

uint32 frame_free(struct avm_frame* frame) {
    heap_free(&frame->heap);
    if (frame->parent == NULL || frame->loader != frame->parent->loader) {
        free(frame->loader);
    }
    free(frame);
    return 0;
}

struct avm_object* avm_load_constant(struct avm_frame* frame, uint32 idx) {
    if (idx == 0)return NULL;
    else {
        P_CONSTANT c = frame->code->constants + idx - 1;
        struct avm_object* obj = gc_alloc();
        switch (obj->tag = c->tag) {
            case TAG_INTEGER:
                obj->val.uival = avm_ntohl(*((uint32*) c->data));
                break;
            case TAG_DOUBLE:
                obj->val.ulval = avm_ntohll(*((uint64*) c->data));
                break;
            default:
                obj->val.str.data = c->data;
                obj->val.str.size = c->size;
                DEBUG("LDC_data=0x%08x\nLDC_size=%d\n", c->data, c->size);
                break;
        }
        return obj;
    }
}

uint32 avm_equals(struct avm_object* o1, struct avm_object* o2) {
    if (o1->tag == o2->tag) {
        if (o1->tag == TAG_DOUBLE) {
            return asDouble(o1) == asDouble(o2);
        } else {
            return asUInt32(o1) == asUInt32(o2);
        }
    } else if (isNumber(o1) && isNumber(o2)) {
        return toDouble(o1) == toDouble(o2);
    }
    return FALSE;
}

struct avm_frame* avm_step(struct avm_frame* frame) {
    //DEBUG("frame=0x%08x; frame->loader=0x%08x; \n",frame,frame->loader);
    //DEBUG("loader{size=%d; left=%d; data=0x%08x; ptr=0x%08x; } \n",
    //	frame->loader->size,
    //	frame->loader->left,
    //	frame->loader->data,
    //	frame->loader->ptr);
    if(frame->loader->left==0)return frame->parent;
    uint32 code = readUInt8(frame->loader);
    //DEBUG("code=%d\n",code);
    DEBUG("avm_step(code=%s)\n", CODE_NAME[code]);
    switch (code) {
        case BYTE_AND:
        {
            uint32 i2 = popUInt32(frame);
            uint32 i1 = popUInt32(frame);
            pushUInt32(frame, i1 & i2);
            break;
        }
        case BYTE_OR:
        {
            uint32 i2 = popUInt32(frame);
            uint32 i1 = popUInt32(frame);
            pushUInt32(frame, i1 | i2);
            break;
        }
        case BYTE_XOR:
        {
            uint32 i2 = popUInt32(frame);
            uint32 i1 = popUInt32(frame);
            pushUInt32(frame, i1 ^ i2);
            break;
        }
        case BYTE_NOT:
        {
            uint32 i = popUInt32(frame);
            pushUInt32(frame, ~i);
            break;
        }
        case BYTE_PLUS:
        {
            struct avm_object* n2 = popObject(frame);
            struct avm_object* n1 = popObject(frame);
            if (!(isNumber(n1) && isNumber(n2))) {
                //fails;
            }
            if (isInteger(n1) && isInteger(n2)) {
                pushUInt32(frame, asInt32(n1) + asInt32(n2));
            } else {
                pushDouble(frame, toDouble(n1) + toDouble(n2));
            }
            gc_free(n1);
            gc_free(n2);
            break;
        }
        case BYTE_MINUS:
        {
            struct avm_object* n2 = popObject(frame);
            struct avm_object* n1 = popObject(frame);
            if (!(isNumber(n1) && isNumber(n2))) {
                //fails;
            }
            if (isInteger(n1) && isInteger(n2)) {
                pushUInt32(frame, asInt32(n1) - asInt32(n2));
            } else {
                pushDouble(frame, toDouble(n1) - toDouble(n2));
            }
            gc_free(n1);
            gc_free(n2);
            break;
        }
        case BYTE_MULTIPLY:
        {
            struct avm_object* n2 = popObject(frame);
            struct avm_object* n1 = popObject(frame);
            if (!(isNumber(n1) && isNumber(n2))) {
                //fails;
            }
            if (isInteger(n1) && isInteger(n2)) {
                pushUInt32(frame, asInt32(n1) * asInt32(n2));
            } else {
                pushDouble(frame, toDouble(n1) * toDouble(n2));
            }
            gc_free(n1);
            gc_free(n2);
            break;
        }
        case BYTE_DIVIDE:
        {
            struct avm_object* n2 = popObject(frame);
            struct avm_object* n1 = popObject(frame);
            if (!(isNumber(n1) && isNumber(n2))) {
                //fails;
            }
            if (isInteger(n1) && isInteger(n2)) {
                pushUInt32(frame, asInt32(n1) / asInt32(n2));
            } else {
                pushDouble(frame, toDouble(n1) / toDouble(n2));
            }
            gc_free(n1);
            gc_free(n2);
            break;
        }
        case BYTE_REM:
        {
            int32 i2 = popUInt32(frame);
            int32 i1 = popUInt32(frame);
            pushUInt32(frame, i1 % i2);
            break;
        }
        case BYTE_NEG:
        {
            struct avm_object* n1 = popObject(frame);
            if (n1->tag == TAG_INTEGER) {
                pushUInt32(frame, -asInt32(n1));
            } else {
                pushDouble(frame, -asDouble(n1));
            }
            gc_free(n1);
            break;
        }
        case BYTE_EQ:
        {
            struct avm_object* n2 = popObject(frame);
            struct avm_object* n1 = popObject(frame);
            pushUInt32(frame, avm_equals(n1, n2));
            gc_free(n1);
            gc_free(n2);
            break;
        }
        case BYTE_NE:
        {
            struct avm_object* n2 = popObject(frame);
            struct avm_object* n1 = popObject(frame);
            pushUInt32(frame, !avm_equals(n1, n2));
            gc_free(n1);
            gc_free(n2);
            break;
        }
        case BYTE_GE:
        {
            struct avm_object* n2 = popObject(frame);
            struct avm_object* n1 = popObject(frame);
            if (!(isNumber(n1) && isNumber(n2))) {
                //fails;
            }
            if (isInteger(n1) && isInteger(n2)) {
                pushUInt32(frame, asInt32(n1) >= asInt32(n2));
            } else {
                pushUInt32(frame, toDouble(n1) >= toDouble(n2));
            }
            gc_free(n1);
            gc_free(n2);
            break;
        }
        case BYTE_GT:
        {
            struct avm_object* n2 = popObject(frame);
            struct avm_object* n1 = popObject(frame);
            if (!(isNumber(n1) && isNumber(n2))) {
                //fails;
            }
            if (isInteger(n1) && isInteger(n2)) {
                pushUInt32(frame, asInt32(n1) > asInt32(n2));
            } else {
                pushUInt32(frame, toDouble(n1) > toDouble(n2));
            }
            gc_free(n1);
            gc_free(n2);
            break;
        }
        case BYTE_LE:
        {
            struct avm_object* n2 = popObject(frame);
            struct avm_object* n1 = popObject(frame);
            if (!(isNumber(n1) && isNumber(n2))) {
                //fails;
            }
            if (isInteger(n1) && isInteger(n2)) {
                pushUInt32(frame, asInt32(n1) <= asInt32(n2));
            } else {
                pushUInt32(frame, toDouble(n1) <= toDouble(n2));
            }
            gc_free(n1);
            gc_free(n2);
            break;
        }
        case BYTE_LT:
        {
            struct avm_object* n2 = popObject(frame);
            struct avm_object* n1 = popObject(frame);
            if (!(isNumber(n1) && isNumber(n2))) {
                //fails;
            }
            if (isInteger(n1) && isInteger(n2)) {
                pushUInt32(frame, asInt32(n1) < asInt32(n2));
            } else {
                pushUInt32(frame, toDouble(n1) < toDouble(n2));
            }
            gc_free(n1);
            gc_free(n2);
            break;
        }
        case BYTE_CONS:
        {
            struct avm_object* n2 = popObject(frame);
            struct avm_object* n1 = popObject(frame);
            struct avm_object* pair = pushObject(frame, gc_alloc());
            pair->tag = TAG_PAIR;
            asPairL(pair) = n1;
            asPairR(pair) = n2;
            //not calling declref() because we need to call inclref() too.
            break;
        }
        case BYTE_CAR:
        {
            struct avm_object* pair = popObject(frame);
            pushObject(frame, asPairL(pair)); //ref+1 inside
            gc_free(pair);
            break;
        }
        case BYTE_CDR:
        {
            struct avm_object* pair = popObject(frame);
            pushObject(frame, asPairR(pair)); //ref+1 inside
            gc_free(pair);
            break;
        }
        case BYTE_IS_PAIR:
        {
            struct avm_object* pair = popObject(frame);
            pushUInt32(frame, pair->tag == TAG_PAIR);
            gc_free(pair);
            break;
        }
        case BYTE_IS_NULL:
        {
            struct avm_object* obj = popObject(frame);
            pushUInt32(frame, obj == NULL);
            break;
        }
        case BYTE_LIST:
        {
            uint32 len = popUInt32(frame);
            struct avm_object* p1;
            struct avm_object* p2;
            struct avm_object* p3;
            p1 = p2 = gc_alloc();
            if (len > 0) {
                p1->val.pair.left = popObject(frame);
                len--;
            }
            for (uint32 i = 0; i < len; i++) {
                p3 = gc_alloc();
                asPairL(p3) = popObject(frame);
                asPairR(p2) = p3;
                gc_inclref(p3);
                p2 = p3;
            }
            pushObject(frame, p1);
            break;
        }
        case BYTE_JZ:
        case BYTE_JNZ:
        case BYTE_JMP:
        {
            int16 offset = readInt16(frame->loader);
            DEBUG("JMP_offset=%d\n",offset);
            if (code == BYTE_JZ && popUInt32(frame) != 0)
                break;
            if (code == BYTE_JNZ && popUInt32(frame) == 0)
                break;
            consumeBytes(frame->loader, offset);
            break;
        }
        case BYTE_LOAD:
        {
            uint16 idx = readUInt16(frame->loader);
            pushObject(frame, frame_heap_get(frame, idx));
            break;
        }
        case BYTE_STORE:
        {
            uint16 idx = readUInt16(frame->loader);
            frame_heap_set(frame, idx, popObject(frame));
            break;
        }
        case BYTE_LDC:
        {
            pushObject(frame, avm_load_constant(frame, readUInt16(frame->loader)));
            break;
        }
        case BYTE_PUSHD:
        {
            struct avm_frame* nf = frame_alloc(frame);
            frame = nf;
            break;
        }
        case BYTE_RET:
        case BYTE_POPD:
        {
            struct avm_frame* of = frame;
            frame = frame->parent;
            frame_free(of);
            break;
        }
        case BYTE_CALL:
        {
            struct avm_object* func = popObject(frame);
            DEBUG("CALL_fun=0x%08x\n", func);
            struct avm_frame* nf = frame_alloc(frame);
            nf->closure = func->val.fun.closure;
            nf->code_section = func->val.fun.section;
            nf->loader = malloc(sizeof (struct avm_loader_status));
            avm_loader_init(nf->loader, func->val.fun.section->data, func->val.fun.section->size);
            uint32 codelen = readUInt32(nf->loader);
            DEBUG("CALL_code_length=%d\n", codelen);
            nf->loader->left=nf->loader->size=codelen;
            frame = nf;
            break;
        }
        case BYTE_LAMBDA:
        {
            struct avm_object* ct = avm_load_constant(frame, readUInt16(frame->loader));
            struct avm_object* fun = gc_alloc();
            struct avm_loader_status l;
            avm_loader_init(&l, ct->val.str.data, ct->val.str.size);
            fun->tag = TAG_FUNCTION;
            uint16 cs = readUInt16(&l);
            DEBUG("LAMBDA_CodeSection=%d\n", cs);
            fun->val.fun.section = &frame->code->sections[cs];
            fun->val.fun.closure = malloc(sizeof (struct avm_heap));
            heap_alloc(fun->val.fun.closure, frame->heap.size);
            if ((cs = readUInt16(&l)) != 0) {
                gc_inclref(fun);
                heap_set(fun->val.fun.closure, cs, fun);
            }
            cs = readUInt16(&l);
            for (int i = 0; i < cs; i++) {
                uint16 id = readUInt16(&l);
                struct avm_object* obj = heap_get(&frame->heap, id);
                gc_inclref(obj);
                heap_set(fun->val.fun.closure, id, obj);
            }
            gc_inclref(fun);
            heap_set(fun->val.fun.closure, 0, fun);
            pushObject(frame, fun);
            gc_free(ct);
            break;
        }
        case BYTE_PUTS:
        {
            struct avm_object* obj = popObject(frame);
            switch (obj->tag) {
                case TAG_STRING:
                case TAG_IDENTIFIER:
                    printf("%s\n", obj->val.str.data);
                    break;
                case TAG_DOUBLE:
                    printf("%f\n", obj->val.dval);
                    break;
                default:
                    printf("%d\n", obj->val.ival);
                    break;
            }
            gc_free(obj);
            break;
        }
    }
    return frame;
}
